package de.mmis.core.base.abstracttree.serializer.selector;

import java.util.Map;

import de.mmis.core.base.DeserializableAnnotations.ObjectDeserializable;
import de.mmis.core.base.abstracttree.LeafNode;
import de.mmis.core.base.abstracttree.Tree;
import de.mmis.core.base.abstracttree.serializer.DeserializableException;
import de.mmis.core.base.abstracttree.serializer.Serializable;
import de.mmis.core.base.abstracttree.serializer.internal.ClassSerializer;
import de.mmis.core.base.abstracttree.serializer.internal.EnumSerializer;
import de.mmis.core.base.abstracttree.serializer.internal.ObjectSerializer;
import de.mmis.core.base.gclass.GArray;
import de.mmis.core.base.gclass.GClass;
import de.mmis.core.base.gclass.GParameterized;

/**
 * generic {@link InstanceSelector} deciding between different atom types, like
 * enums, byte arrays, Serializable types, {@link Tree}s, Java-Classes
 * 
 * @author Christian Koch
 * 
 */
public class GenericAtomSelector extends InstanceSelector {

	private final LeafNode atom;
	private final ClassLoader classLoader;

	public GenericAtomSelector(LeafNode atom, ClassLoader classLoader,
			String id, Map<String, Object> idmap) {
		super(id, idmap);
		this.atom = atom;
		this.classLoader = classLoader;
	}

	@Override
	public Object createInstance(GClass gclass)
			throws InstanceSelectorException {
		Object ret = createInstanceInternal(gclass);
		setID(ret);
		return ret;
	}

	private Object createInstanceInternal(GClass gclass)
			throws InstanceSelectorException {
		if (gclass.equalsClass(Object.class)
				|| gclass.equalsClass(byte[].class)
				|| (gclass instanceof GArray && ((GArray) gclass)
						.getComponentType().equalsClass(byte.class)))
			return this.atom.getData().clone();

		if (gclass.equalsClass(Byte[].class)
				|| (gclass instanceof GArray && ((GArray) gclass)
						.getComponentType().equalsClass(Byte.class))) {
			Byte[] ret = new Byte[this.atom.getData().length];
			for (int i = 0; i < ret.length; i++)
				ret[i] = new Byte(this.atom.getData()[i]);
			return ret;
		}

		if (gclass.equalsClass(Object[].class)
				|| (gclass instanceof GArray && ((GArray) gclass)
						.getComponentType().equalsClass(Object.class))) {
			Object[] ret = new Object[this.atom.getData().length];
			for (int i = 0; i < ret.length; i++)
				ret[i] = new Byte(this.atom.getData()[i]);
			return ret;
		}

		if (gclass.equalsClass(Class.class)) {
			try {
				return ClassSerializer
						.getAtomClass(this.atom, this.classLoader);
			} catch (DeserializableException e) {
				throw new InstanceSelectorException(
						"cannot convert generic atom \"" + this.atom
								+ "\" to class \"" + gclass + "\"", e);
			}
		}

		if (gclass instanceof GParameterized) {
			Class<?> base = ((GParameterized) gclass).getBaseClass();
			if (base.isEnum()) {
				try {
					return EnumSerializer.getEnumConstant(this.atom, base);
				} catch (DeserializableException e) {
					throw new InstanceSelectorException("item \"" + this.atom
							+ "\" cannot be converted to enum \""
							+ base.getName() + "\"", e);
				}
			}

			if (base.getAnnotation(ObjectDeserializable.class) != null) {
				try {
					return ObjectSerializer.deserialize(
							new Tree[] { this.atom }, this.classLoader,
							getID(), getIDMap()).createInstance(gclass);
				} catch (DeserializableException e) {
					throw new InstanceSelectorException("item \"" + this.atom
							+ "\" cannot be converted to type \""
							+ base.getName() + "\"", e);
				}
			}

			if (Serializable.class.isAssignableFrom(base)) {
				return new SerializableSelector(new Tree[] { this.atom },
						this.classLoader, getID(), getIDMap())
						.createInstance(gclass);
			}
		}

		throw new InstanceSelectorException("cannot convert generic atom \""
				+ this.atom + "\" to class \"" + gclass + "\"");
	}
}
